/** * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.seedstack.seed.core; import com.google.common.base.Strings; import io.nuun.kernel.api.Kernel; import io.nuun.kernel.api.config.KernelConfiguration; import org.fusesource.jansi.Ansi; import org.fusesource.jansi.AnsiRenderer; import org.seedstack.coffig.Coffig; import org.seedstack.seed.ApplicationConfig; import org.seedstack.seed.LoggingConfig; import org.seedstack.seed.ProxyConfig; import org.seedstack.seed.SeedException; import org.seedstack.seed.core.internal.CoreErrorCode; import org.seedstack.seed.core.internal.diagnostic.DiagnosticManagerImpl; import org.seedstack.seed.core.internal.init.AutodetectLogManager; import org.seedstack.seed.core.internal.init.BaseConfiguration; import org.seedstack.seed.core.internal.init.ConsoleManager; import org.seedstack.seed.core.internal.init.GlobalValidatorFactory; import org.seedstack.seed.core.internal.init.KernelManager; import org.seedstack.seed.core.internal.init.LogManager; import org.seedstack.seed.core.internal.init.ProxyManager; import org.seedstack.seed.diagnostic.DiagnosticManager; import org.seedstack.seed.spi.SeedInitializer; import org.seedstack.shed.ClassLoaders; import org.seedstack.shed.reflect.Classes; import org.seedstack.shed.text.TextTemplate; import javax.annotation.Nullable; import javax.validation.ValidatorFactory; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Scanner; import java.util.ServiceLoader; import java.util.Set; /** * This class is the Seed framework entry point, which is used create and dispose kernels. * It handles global initialization and cleanup. */ public class Seed { private static final String WELCOME_MESSAGE = "\n" + " ____ _ ____ _ _ \n" + "/ ___| ___ ___ __| / ___|| |_ __ _ ___| | __\n" + "\\___ \\ / _ \\/ _ \\/ _ \\___ \\| __/ _ |/ __| |/ /\n" + " ___) | __/ __/ (_| |___) | || (_| | (__| < \n" + "|____/ \\___|\\___|\\____|____/ \\__\\____|\\___|_|\\_\\"; private static volatile boolean initialized = false; private static volatile boolean noLogs = false; private static final DiagnosticManager diagnosticManager = new DiagnosticManagerImpl(); private final String seedVersion; private final String businessVersion; private final ApplicationConfig applicationConfig; private final Coffig configuration; private final ConsoleManager consoleManager; private final LogManager logManager; private final ValidatorFactory validatorFactory; private final ProxyManager proxyManager; private final KernelManager kernelManager; private final Set<SeedInitializer> seedInitializers = new HashSet<>(); private static class Holder { private static final Seed INSTANCE = new Seed(); } /** * Disable logs globally if a supported SLF4J implementation is used. Currently only Logback is supported. */ public static void disableLogs() { noLogs = true; } /** * Create and start a basic kernel without specifying a runtime context, nor a configuration. Seed JVM-global * state is automatically initialized before the first time a kernel is created. * * @return the {@link Kernel} instance. */ public static Kernel createKernel() { return createKernel(null, null, true); } /** * Create, initialize and optionally start a kernel with the specified runtime context and configuration. Seed JVM-global * state is automatically initialized before the first time a kernel is created. * * @param runtimeContext the runtime context object, which will be accessible from plugins. * @param kernelConfiguration the kernel configuration. * @param autoStart if true, the kernel is started automatically. * @return the {@link Kernel} instance. */ public static Kernel createKernel(@Nullable Object runtimeContext, @Nullable KernelConfiguration kernelConfiguration, boolean autoStart) { Seed instance = getInstance(); return instance.kernelManager.createKernel( SeedRuntime.builder() .context(runtimeContext) .diagnosticManager(diagnosticManager) .configuration(instance.configuration.fork()) .validatorFactory(instance.validatorFactory) .applicationConfig(instance.applicationConfig) .version(instance.seedVersion) .businessVersion(instance.businessVersion) .build(), kernelConfiguration, autoStart); } /** * Stops and dispose a running {@link Kernel} instance. * * @param kernel the kernel to dispose. */ public static void disposeKernel(Kernel kernel) { KernelManager.get().disposeKernel(kernel); } /** * Provides the default {@link DiagnosticManager} instance to dump diagnostics outside a running kernel. * * @return the default diagnostic manager. */ public static DiagnosticManager diagnostic() { return diagnosticManager; } /** * Provides the application base configuration (i.e. not including configuration sources discovered after kernel * startup). * * @return the {@link Coffig} object for application base configuration. */ public static Coffig baseConfiguration() { return getInstance().configuration; } /** * Cleanup Seed JVM-global state explicitly. Should be done before exiting the JVM. After calling this method * Seed is no longer usable in the current JVM. */ public static void close() { if (initialized) { Seed instance = getInstance(); instance.seedInitializers.forEach(SeedInitializer::onClose); instance.proxyManager.uninstall(); instance.validatorFactory.close(); instance.consoleManager.uninstall(); instance.logManager.close(); Thread.setDefaultUncaughtExceptionHandler(null); initialized = false; } } private static Seed getInstance() { try { return Holder.INSTANCE; } catch (Throwable t) { if (t instanceof ExceptionInInitializerError) { t = t.getCause(); } throw SeedException.wrap(t, CoreErrorCode.UNABLE_TO_INITIALIZE_SEED); } } private Seed() { seedVersion = Optional.ofNullable(Seed.class.getPackage()) .map(Package::getImplementationVersion) .orElse(null); businessVersion = Classes.optional("org.seedstack.business.internal.BusinessSpecifications") .map(Class::getPackage) .map(Package::getImplementationVersion) .orElse(null); Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { if (throwable instanceof SeedException) { throwable.printStackTrace(System.err); } else { SeedException.wrap(throwable, CoreErrorCode.UNEXPECTED_EXCEPTION).printStackTrace(System.err); } }); // Logging initialization (should silence logs until logging activation later in the initialization) logManager = AutodetectLogManager.get(); // Validation validatorFactory = GlobalValidatorFactory.get(); // Configuration configuration = BaseConfiguration.get(); applicationConfig = configuration.get(ApplicationConfig.class); // Console consoleManager = ConsoleManager.get(); consoleManager.install(applicationConfig.getColorOutput()); // Banner if (!noLogs && applicationConfig.isPrintBanner()) { System.out.println(buildBannerMessage(applicationConfig).orElseGet(this::buildWelcomeMessage)); } // Logging activation if (!noLogs) { logManager.configure(configuration.get(LoggingConfig.class)); } // Proxy proxyManager = ProxyManager.get(); proxyManager.install(configuration.get(ProxyConfig.class)); // Nuun kernelManager = KernelManager.get(); // Custom initializers for (SeedInitializer seedInitializer : ServiceLoader.load(SeedInitializer.class)) { try { seedInitializer.onInitialization(configuration); seedInitializers.add(seedInitializer); } catch (Exception e) { throw SeedException.wrap(e, CoreErrorCode.ERROR_IN_INITIALIZER) .put("initializerClass", seedInitializer.getClass().getName()); } } initialized = true; } private Optional<String> buildBannerMessage(ApplicationConfig applicationConfig) { String banner = getBanner(); if (banner != null) { Map<String, Object> bannerReplacements = new HashMap<>(); bannerReplacements.put("seed.version", seedVersion); bannerReplacements.put("business.version", businessVersion); bannerReplacements.put("app.id", applicationConfig.getId()); bannerReplacements.put("app.name", applicationConfig.getName()); bannerReplacements.put("app.version", applicationConfig.getVersion()); return Optional.of(AnsiRenderer.render(new TextTemplate(banner).render(bannerReplacements))); } else { return Optional.empty(); } } private String buildWelcomeMessage() { Ansi welcomeMessage = Ansi.ansi().reset().fgBrightGreen().a(WELCOME_MESSAGE).reset(); if (seedVersion != null) { welcomeMessage.a("\n").a("Core v").a(Strings.padEnd(seedVersion, 16, ' ')); } if (businessVersion != null) { welcomeMessage.a(seedVersion != null ? "" : "\n").a("Business v").a(businessVersion); } welcomeMessage.a("\n"); return welcomeMessage.reset().toString(); } private String getBanner() { InputStream bannerStream = ClassLoaders.findMostCompleteClassLoader(Seed.class).getResourceAsStream("banner.txt"); if (bannerStream != null) { try { return new Scanner(bannerStream).useDelimiter("\\Z").next(); } finally { try { bannerStream.close(); } catch (IOException e) { // nothing to do } } } return null; } }